﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using INF1010_Chat;
using System.Net.Sockets;
using System.Threading;

namespace ClientChat
{
    public class ClientChat
    {
        public delegate void ReceivedDisconnectNoticeEventHandler(object sender, EventArgs args);
        public event ReceivedDisconnectNoticeEventHandler ClientDisconnected;

        public delegate void ReceivedRefreshUsersEventHandler(object sender, EventArgs args);
        public event ReceivedRefreshUsersEventHandler RefreshList;

        public delegate void UserMessageReceiveEventHandler(object sender, EventArgs args);
        public event UserMessageReceiveEventHandler UserMessageReceive;

        private static ClientChat clientSingleton;


        private User user;
        private UserList users;

        private Queue<string> userMessages;

        private ClientChat()
        {
            users = new UserList();
            userMessages = new Queue<string>();
        }

        /*
         * Retourne l'instance du client 
         */
        public static ClientChat GetClientChat()
        {
            if (clientSingleton == null)
                clientSingleton = new ClientChat();

            return clientSingleton;
        }

        public List<User> Users
        {
            get
            {
                return users.Users.ToList();
            }
        }

        /*
         * Retourne le nom de l'utilisateur connecté 
         */
        public string UserName
        {
            get
            {
                if (user != null)
                    return user.Name;
                else
                    return String.Empty;
            }
        }

        public bool IsConnected
        {
            get
            {
                return (user != null && user.CommunicationSocket != null && user.CommunicationSocket.Connected);
            }
        }

        /*
         * Envoi une requête de connexion 
         */
        public void SendConnect(string userName, string ipAdress, int port)
        {
            user = new User(userName);
            //user.CommunicationSocket.
            user.CommunicationSocket.Connect(ipAdress, port);
            Send(Commands.Authentify + user.Name);
            user.SomethingReceived += new User.SomethingReceivedEventHandler(user_SomethingReceived);
            user.StartThreadReceive();
        }

        /*
         * Évènement lorsque quelque chose est reçu
         */
        void user_SomethingReceived(object sender, EventArgs args)
        {
            ParseResponse(user.DequeueMessage());
        }

        /*
         * Envoi un avis de déconnexion 
         */
        public void SendDisconnect()
        {
            Send(Commands.Unauthentify);
            user.CommunicationSocket.Shutdown(SocketShutdown.Send);
            user.CommunicationSocket.Disconnect(true);
            user = null;
        }

        /*
         * Envoi un message d'adhésion à un groupe 
         */
        public void SendJoinGroup(string groupName)
        {
            Group group = user.AddGroup(groupName);
            if (group != null)
                Send(Commands.JoinGroup + groupName);
        }

        /*
         * Envoi un message de départ d'un groupe
         */
        public void SendLeaveGroup(string groupName)
        {
            user.RemoveGroup(groupName);
            Send(Commands.LeaveGroup + groupName);
        }

        /*
         * Envoi une requête de liste d'utilisateurs 
         */
        public void SendRefreshUserList()
        {
            Send(Commands.SendListUser);
        }

        /*
         * Envoi un message broadcast 
         */
        public string SendMessageToAll(string message)
        {
            Send(Commands.SendToAll + message);
            return "Vous --> Tous :" + message;
        }

        /*
         * Envoi un message multicast 
         */
        public string SendMessageToGroup(string groupName, string message)
        {
            Send(Commands.SendToGroup + "{" + groupName + "}" + message);
            return "Vous --> Group { " + groupName + " } :" + message;
        }

        /*
         * Envoi un message unicast 
         */
        public string SendMessageToUser(string userName, string message)
        {
            Send(Commands.SendToUser + "{" + userName + "}" + message);
            return "Vous --> { " + userName + " } :" + message;
        }

        /*
         * Envoi le message encapsulé 
         */
        public void Send(string message)
        {
            if (user != null)
            {
                Socket socketCommunication = user.CommunicationSocket;
                if (socketCommunication != null)
                {
                    byte[] buffer = Encoding.Default.GetBytes(message);
                    socketCommunication.Send(buffer);
                }
                else
                    throw new NotConnectedToServerException("Vous n'êtes pas connecté au serveur.");
            }
            else
                throw new NotAuthenticatedException("Vous devez vous authentifier pour pouvoir communiquer");

        }

        /*
         * Traite la réponse du serveur
         */
        public void ParseResponse(string message)
        {
            string command = message.Substring(0, 2);
            message = message.Substring(2);

            switch (command)
            {
                case Commands.SendToUser:
                    userMessages.Enqueue(message);
                    OnNewMessageReceive(new EventArgs());
                    break;
                case Commands.SendToGroup:
                    userMessages.Enqueue(message);
                    OnNewMessageReceive(new EventArgs());
                    break;

                case Commands.SendToAll:
                    userMessages.Enqueue(message);
                    OnNewMessageReceive(new EventArgs());
                    break;

                case Commands.SendListUser:
                    this.users = UserList.Deserialize(message);
                    OnRefreshList(new EventArgs());
                    break;
                case Commands.Unauthentify:
                    OnClientDisconnected(new EventArgs());
                    user.CommunicationSocket.Shutdown(SocketShutdown.Send);
                    user.CommunicationSocket.Disconnect(true);
                    user = null;
                    break;

                //case Commands.Disconnect:
                //    userMessages.Enqueue(message);
                //    user.CommunicationSocket.Disconnect(true);
                //    break;
            }
        }

        /*
         * Défile un message de la file 
         */
        public string DequeueUserMessage()
        {
            return userMessages.Dequeue();
        }

        /*
         * Évènement lorsque le client se déconnecte 
         */
        public virtual void OnClientDisconnected(EventArgs args)
        {
            if (ClientDisconnected != null)
                ClientDisconnected(this, args);
        }

        /*
         * Évènement lorsque la liste d'utilisateur est rafraichie 
         */
        public virtual void OnRefreshList(EventArgs args)
        {
            if (RefreshList != null)
                RefreshList(this, args);
        }

        /*
         * Évènement lors de la réception de message 
         */
        public virtual void OnNewMessageReceive(EventArgs args)
        {
            if (UserMessageReceive != null)
                UserMessageReceive(this, args);
        }
    }

    public class NotAuthenticatedException : Exception
    {
        public NotAuthenticatedException(string message)
            : base(message)
        {

        }
    }
    public class NotConnectedToServerException : Exception
    {
        public NotConnectedToServerException(string message)
            : base(message)
        {

        }
    }
}
